package org.easier.framework.http.apache.impl;


import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.SSLInitializationException;
import org.apache.http.util.EntityUtils;
import org.easier.framework.http.HttpAdapter;
import org.easier.framework.http.HttpByteResult;
import org.easier.framework.http.HttpResult;
import org.easier.framework.json.JsonAgent;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * http请求客户端
 */
public class HttpClient implements HttpAdapter {
    private final static int TIMEOUT_CODE = 408;
    private String baseUri;
    private String certPathname;
    private String certPassword;
    private CloseableHttpClient httpClient = null;
    private IdleConnectionMonitorThread idleConnectionMonitorThread = null;
    private int connectMaxTotal = 200;// 连接池最大连接数
    private int connectMaxDefault = 20;// 连接池每个连接最大数
    private int connectRecyclingTime = 5;// 连接回收检测时间(秒)
    private int connectFreeTimeout = 30;// 连接空闲回收时间(秒)
    private int connectKeepAliveDuration = 30;// 连接维持时间(秒)
    private int connectRetryNumber = 5;

    private String proxyUri;//代理ip
    private int proxyPort;//代理端口
    private String proxyScheme;//代理协议
    private String proxyUserName;//代理服务器用户名
    private String proxyPassword;//代理服务器密码

    private Charset charset = Consts.UTF_8;
    //每个请求的超时时间
    private int connectTimeout = 5 * 60 * 1000;//默认连接超时时间
    private int connectRequestTimeout = 5 * 60 * 1000;//默认请求超时时间
    private int socketTimeout = 5 * 60 * 1000;//默认socket超时时间

    public HttpClient() {
        afterPropertiesSet();
    }

    public HttpClient(int connectMaxTotal, int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout,
                      int connectKeepAliveDuration, int connectRetryNumber, String proxyUri, int proxyPort, String proxyScheme, String charset) {
        this.connectMaxTotal = connectMaxTotal;
        this.connectMaxDefault = connectMaxDefault;
        this.connectRecyclingTime = connectRecyclingTime;
        this.connectFreeTimeout = connectFreeTimeout;
        this.connectKeepAliveDuration = connectKeepAliveDuration;
        this.connectRetryNumber = connectRetryNumber;
        this.charset = Charset.forName(charset);
        this.proxyUri = proxyUri;
        this.proxyPort = proxyPort;
        this.proxyScheme = proxyScheme;
        afterPropertiesSet();
    }

    public HttpClient(String baseUri, String certPathname, String certPassword, int connectMaxTotal,
                      int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout, int connectKeepAliveDuration,
                      int connectRetryNumber, String charset) {
        this.baseUri = baseUri;
        this.certPathname = certPathname;
        this.certPassword = certPassword;
        this.connectMaxTotal = connectMaxTotal;
        this.connectMaxDefault = connectMaxDefault;
        this.connectRecyclingTime = connectRecyclingTime;
        this.connectFreeTimeout = connectFreeTimeout;
        this.connectKeepAliveDuration = connectKeepAliveDuration;
        this.connectRetryNumber = connectRetryNumber;
        this.charset = Charset.forName(charset);
        afterPropertiesSet();
    }

    private void afterPropertiesSet() {
        PoolingHttpClientConnectionManager cm = getPoolingHttpClientConnectionManager();
        ConnectionKeepAliveStrategy myStrategy = getConnectionKeepAliveStrategy();
        HttpRequestRetryHandler myRetryHandler = getHttpRequestRetryHandler();
        HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(cm)// 设置连接池
                .setKeepAliveStrategy(myStrategy)// 设置连接存活策略
                .setRetryHandler(myRetryHandler);// 设置请求重试;

        if (StringUtils.isNotEmpty(proxyUri) && proxyPort != 0) {
            HttpHost httpHost = new HttpHost(proxyUri, proxyPort, proxyScheme);
            httpClientBuilder.setProxy(httpHost);
            if (StringUtils.isNotEmpty(proxyUserName) && StringUtils.isNotEmpty(proxyPassword)) {
                CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(new AuthScope(httpHost),
                        new UsernamePasswordCredentials(proxyUserName, proxyPassword));
                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }
        }
        httpClient = httpClientBuilder.build();
        idleConnectionMonitorThread = new IdleConnectionMonitorThread(cm, connectRecyclingTime, connectFreeTimeout);// 设置连接回收策略
        idleConnectionMonitorThread.start();
    }

    /**
     * httpGet请求
     *
     * @param uri uri地址
     * @return http返回值
     */
    public <T> T get(String uri, Class<T> resultClass) {
        HttpResult result = get(uri, null, null);
        if (result.getHttpCode() != 200) {
            return null;
        }
        return JsonAgent.deserialize(result.getContent(), resultClass);
    }


    /**
     * httpGet请求
     *
     * @param uri uri地址
     * @return http返回值
     */
    public HttpResult get(String uri) {
        return get(uri, null, null);
    }

    /**
     * httpGet请求
     *
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     */
    public HttpResult get(Map<String, String> paramMap, Map<String, String> headerMap) {
        return get(baseUri, paramMap, headerMap);
    }

    /**
     * httpGet请求
     *
     * @param paramMap 请求参数
     * @return http返回值
     */
    public HttpResult get(Map<String, String> paramMap) {
        return get(baseUri, paramMap, null);
    }

    /**
     * httpGet请求
     *
     * @param uri      uri地址
     * @param paramMap 请求参数
     * @return http返回值
     */
    public HttpResult get(String uri, Map<String, String> paramMap) {
        return get(uri, paramMap, null);
    }


    @Override
    public HttpResult get(String uri, Map<String, String> paramMap, Map<String, String> headerMap) {
        try {
            if (paramMap != null) {
                uri = uri + "?" + StringUtils.join(paramMap.entrySet().stream().
                        map(p -> p.getKey() + "=" + p.getValue()).collect(Collectors.toList()), '&');
            }
            return getCall(uri, headerMap, connectTimeout, connectRequestTimeout, socketTimeout);
        } catch (IOException ex) {
            throw new RuntimeException(ex.toString());
        }
    }

    /**
     * postFrom请求
     *
     * @param paramMap 请求参数
     * @return http返回值
     */
    public HttpResult postForm(Map<String, String> paramMap) {
        return postForm(baseUri, paramMap, null);
    }

    /**
     * postFrom请求
     *
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     */
    public HttpResult postForm(Map<String, String> paramMap, Map<String, String> headerMap) {
        return postForm(baseUri, paramMap, headerMap);
    }

    /**
     * postFrom请求
     *
     * @param uri      uri地址
     * @param paramMap 请求参数
     * @return http返回值
     */
    public HttpResult postForm(String uri, Map<String, String> paramMap) {
        return postForm(uri, paramMap, null);
    }


    @Override
    public HttpResult postForm(String uri, Map<String, String> paramMap, Map<String, String> headerMap) {
        try {
            return postFormCall(uri, paramMap, headerMap, connectTimeout, connectRequestTimeout, socketTimeout);
        } catch (IOException ex) {
            return new HttpResult(TIMEOUT_CODE, ex.toString());
        }
    }

    /**
     * postFrom请求
     *
     * @param uri       uri地址
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     */
    public HttpByteResult postByteForm(String uri, Map<String, String> paramMap, Map<String, String> headerMap) {
        try {
            return postByteFormCall(uri, paramMap, headerMap, connectTimeout, connectRequestTimeout, socketTimeout);
        } catch (IOException ex) {
            return new HttpByteResult(TIMEOUT_CODE, ex.toString().getBytes());
        }
    }

    /**
     * postJson请求
     *
     * @param requestJson 请求参数
     * @return http返回值
     */
    public HttpResult postJson(String requestJson) {
        return postJson(baseUri, requestJson, null);
    }

    /**
     * postJson请求
     *
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     */
    public HttpResult postJson(String requestJson, Map<String, String> headerMap) {
        return postJson(baseUri, requestJson, headerMap);
    }


    /**
     * postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @return http返回值
     */
    public HttpResult postJson(String uri, String requestJson) {
        return postJson(uri, requestJson, null);
    }


   @Override
    public HttpResult postJson(String uri, String requestJson, Map<String, String> headerMap) {
        try {
            return postJsonCall(uri, false, requestJson, headerMap);
        } catch (IOException ex) {
            return new HttpResult(TIMEOUT_CODE, ex.toString());
        }
    }

   @Override
    public <T> T postJson(String uri, Object requestJson, Map<String, String> headerMap, Class<T> resultClass) {
        HttpResult result = postJson(uri, JsonAgent.serialize(requestJson), headerMap);
        if (result.getHttpCode() != 200)
            return null;
        return JsonAgent.deserialize(result.getContent(), resultClass);
    }

    /**
     * 请求的postJson请求
     *
     * @param requestJson 请求参数
     * @return http返回值
     */
    public HttpResult postJsonUseCert(String requestJson) {
        return postJsonUseCert(baseUri, requestJson, null);
    }

    /**
     * 请求的postJson请求
     *
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     */
    public HttpResult postJsonUseCert(String requestJson, Map<String, String> headerMap) {
        return postJsonUseCert(baseUri, requestJson, headerMap);
    }

    /**
     * 请求的postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @return http返回值
     */
    public HttpResult postJsonUseCert(String uri, String requestJson) {
        return postJsonUseCert(uri, requestJson, null);
    }

    /**
     * 请书的postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     */
    public HttpResult postJsonUseCert(String uri, String requestJson, Map<String, String> headerMap) {
        try {
            return postJsonCall(uri, true, requestJson, headerMap);
        } catch (IOException ex) {
            return new HttpResult(TIMEOUT_CODE, ex.toString());
        }
    }


    /**
     * 下载文件
     *
     * @param uri      uri地址
     * @param filePath 文件路径
     * @return http返回流
     * @throws IOException
     */
    public boolean downloadFile(String uri, String filePath) throws IOException {
        return downloadFile(uri, filePath, null, connectTimeout, connectRequestTimeout, socketTimeout);
    }

    /**
     * 下载文件
     *
     * @param uri uri地址
     * @return http返回流
     * @throws IOException
     */
    public HttpByteResult downloadFile(String uri) throws IOException {
        return downloadFile(uri, null, connectTimeout, connectRequestTimeout, socketTimeout);
    }


    /**
     * 下载文件
     *
     * @param uri                   uri地址
     * @param filePath              文件路径
     * @param headerMap             http请求头
     * @param connectTimeout        连接超时
     * @param connectRequestTimeout 请求超时
     * @param socketTimeout         socket超时
     * @return http返回流
     * @throws IOException
     */
    public boolean downloadFile(String uri, String filePath, Map<String, String> headerMap,
                                int connectTimeout, int connectRequestTimeout, int socketTimeout) throws IOException {
        if (!createFolder(filePath))
            return false;
        HttpGet httpGet = new HttpGet(uri);
        Header[] headers = getHeaders(headerMap);
        if (headers.length != 0)
            httpGet.setHeaders(headers);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpGet.setConfig(requestConfig);
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            Header lenHeader = response.getFirstHeader("Content-Length");
            if (lenHeader != null && "0".equals(lenHeader.getValue())) {
                return false;
            }
            InputStream stream = response.getEntity().getContent();
            try (FileOutputStream fs = new FileOutputStream(filePath)) {
                byte[] buffer = new byte[1024 * 4];
                int byteRead = 0;
                while ((byteRead = stream.read(buffer)) != -1)
                    fs.write(buffer, 0, byteRead);
                fs.flush();
            }
        }
        return true;
    }

    /**
     * 下载文件
     *
     * @param uri                   uri地址
     * @param headerMap             http请求头
     * @param connectTimeout        连接超时
     * @param connectRequestTimeout 请求超时
     * @param socketTimeout         socket超时
     * @return http返回流
     * @throws IOException
     */
    public HttpByteResult downloadFile(String uri, Map<String, String> headerMap,
                                       int connectTimeout, int connectRequestTimeout, int socketTimeout) throws IOException {
        HttpGet httpGet = new HttpGet(uri);
        Header[] headers = getHeaders(headerMap);
        if (headers.length != 0)
            httpGet.setHeaders(headers);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpGet.setConfig(requestConfig);
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            Header lenHeader = response.getFirstHeader("Content-Length");
            if (lenHeader != null && "0".equals(lenHeader.getValue())) {
                return null;
            }

            if (response.getEntity().getContentLength() >= 2147483647L) {
                return null;
            }

            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int len = 0;
            while ((len = response.getEntity().getContent().read(buff, 0, 1024)) != -1) {
                swapStream.write(buff, 0, len);
            }
            return new HttpByteResult(response.getStatusLine().getStatusCode(), swapStream.toByteArray());
        }
    }

    /**
     * 关闭方法
     */
    private void close() throws IOException {
        synchronized (this) {
            if (httpClient != null)
                httpClient.close();
            if (idleConnectionMonitorThread != null)
                idleConnectionMonitorThread.shutdown();
        }
    }

    /**
     * 发送post表单请求
     *
     * @param uri                   uri地址
     * @param map                   请求参数
     * @param headerMap             http请求头
     * @param connectTimeout        连接超时
     * @param connectRequestTimeout 请求超时
     * @param socketTimeout         socket超时
     * @return http返回值
     * @throws IOException
     */
    private HttpResult postFormCall(String uri, Map<String, String> map, Map<String, String> headerMap,
                                    int connectTimeout, int connectRequestTimeout, int socketTimeout) throws IOException {
        HttpPost httpPost = new HttpPost(getStandardUri(uri));
        List<NameValuePair> formParams = map.entrySet().stream().
                map(p -> new BasicNameValuePair(p.getKey(), p.getValue())).collect(Collectors.toList());
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, charset);
        httpPost.setEntity(entity);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpPost.setConfig(requestConfig);
        return httpCall(httpPost, httpClient, headerMap);
    }


    /**
     * 发送post表单请求
     *
     * @param uri                   uri地址
     * @param map                   请求参数
     * @param headerMap             http请求头
     * @param connectTimeout        连接超时
     * @param connectRequestTimeout 请求超时
     * @param socketTimeout         socket超时
     * @return http返回值
     * @throws IOException
     */
    private HttpByteResult postByteFormCall(String uri, Map<String, String> map, Map<String, String> headerMap,
                                            int connectTimeout, int connectRequestTimeout, int socketTimeout) throws IOException {
        HttpPost httpPost = new HttpPost(getStandardUri(uri));
        List<NameValuePair> formParams = map.entrySet().stream().
                map(p -> new BasicNameValuePair(p.getKey(), p.getValue())).collect(Collectors.toList());
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, charset);
        httpPost.setEntity(entity);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpPost.setConfig(requestConfig);
        return httpByteCall(httpPost, httpClient, headerMap);
    }

    /**
     * @param baseUri     http请求地址
     * @param isUseCert   是否使用证书
     * @param requestJson 请求内容
     * @return 返回值
     * @throws IOException
     */
    private HttpResult postJsonCall(String baseUri, boolean isUseCert, String requestJson, Map<String, String> headerMap) throws IOException {
        try {
            return postCall(baseUri, requestJson, "application/json", isUseCert, headerMap,
                    connectTimeout, connectRequestTimeout, socketTimeout);
        } catch (UnrecoverableKeyException | SSLInitializationException | KeyManagementException | KeyStoreException
                | NoSuchAlgorithmException | CertificateException e) {
            throw new RuntimeException(e);
        }
    }

    public HttpResult postCall(String baseUri, String contentType, boolean isUseCert, String requestJson, Map<String, String> headerMap) throws IOException {
        try {
            return postCall(baseUri, requestJson, contentType, isUseCert, headerMap,
                    connectTimeout, connectRequestTimeout, socketTimeout);
        } catch (UnrecoverableKeyException | SSLInitializationException | KeyManagementException | KeyStoreException
                | NoSuchAlgorithmException | CertificateException e) {
            throw new RuntimeException(e);
        }
    }

    public HttpByteResult postByteCall(String baseUri, String contentType, boolean isUseCert, String requestJson, Map<String, String> headerMap) throws IOException {
        try {
            return postByteCall(baseUri, requestJson, contentType, isUseCert, headerMap,
                    connectTimeout, connectRequestTimeout, socketTimeout);
        } catch (UnrecoverableKeyException | SSLInitializationException | KeyManagementException | KeyStoreException
                | NoSuchAlgorithmException | CertificateException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 以json格式发送post请求
     *
     * @param uri                   uri地址
     * @param requestContent        请求参数
     * @param contentType           Content-Type
     * @param isUseCert             是否使用证书
     * @param headerMap             http请求头
     * @param connectTimeout        连接超时
     * @param connectRequestTimeout 请求超时
     * @param socketTimeout         socket请时
     * @return http返回值
     * @throws KeyStoreException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     * @throws SSLInitializationException
     * @throws KeyManagementException
     * @throws IOException
     */
    private HttpResult postCall(String uri, String requestContent, String contentType, boolean isUseCert,
                                Map<String, String> headerMap, int connectTimeout, int connectRequestTimeout, int socketTimeout)
            throws KeyStoreException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException, SSLInitializationException, KeyManagementException, IOException {
        CloseableHttpClient httpclient;
        if (isUseCert) {
            httpclient = getSSLHttpClient();
        } else {
            httpclient = httpClient;
        }

        HttpPost httpPost = new HttpPost(getStandardUri(uri));
        StringEntity stringEntity = new StringEntity(requestContent, charset);
        stringEntity.setContentType(contentType);
        httpPost.setEntity(stringEntity);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpPost.setConfig(requestConfig);
        try {
            return httpCall(httpPost, httpclient, headerMap);
        } finally {
            if (isUseCert)
                httpclient.close();
        }
    }

    private HttpByteResult postByteCall(String uri, String requestContent, String contentType, boolean isUseCert,
                                        Map<String, String> headerMap, int connectTimeout, int connectRequestTimeout, int socketTimeout)
            throws KeyStoreException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException, SSLInitializationException, KeyManagementException, IOException {
        CloseableHttpClient httpclient;
        if (isUseCert) {
            httpclient = getSSLHttpClient();
        } else {
            httpclient = httpClient;
        }

        HttpPost httpPost = new HttpPost(getStandardUri(uri));
        StringEntity stringEntity = new StringEntity(requestContent, charset);
        stringEntity.setContentType(contentType);
        httpPost.setEntity(stringEntity);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpPost.setConfig(requestConfig);
        try {
            return httpByteCall(httpPost, httpclient, headerMap);
        } finally {
            if (isUseCert)
                httpclient.close();
        }
    }

    /**
     * 发送get请求
     *
     * @param uri                   uri地址
     * @param headerMap             http请求头
     * @param connectTimeout        连接超时
     * @param connectRequestTimeout 请求超时
     * @param socketTimeout         socket请时
     * @return http返回值
     * @throws ClientProtocolException
     * @throws IOException
     */
    private HttpResult getCall(String uri, Map<String, String> headerMap,
                               int connectTimeout, int connectRequestTimeout, int socketTimeout) throws ClientProtocolException, IOException {
        HttpGet httpGet = new HttpGet(getStandardUri(uri));
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpGet.setConfig(requestConfig);
        return httpCall(httpGet, httpClient, headerMap);
    }

    /**
     * @param request    httpRequest
     * @param httpClient
     * @param headerMap  http请求头
     * @return http返回值
     * @throws IOException
     */
    private HttpResult httpCall(final HttpUriRequest request, CloseableHttpClient httpClient, Map<String, String> headerMap) throws IOException {
        Header[] headers = getHeaders(headerMap);
        if (headers.length != 0)
            request.setHeaders(headers);
        try (CloseableHttpResponse response = httpClient.execute(request)) {
            return new HttpResult(response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), charset));
        }
    }

    /**
     * @param request    httpRequest
     * @param httpClient
     * @param headerMap  http请求头
     * @return http返回值
     * @throws IOException
     */
    private HttpByteResult httpByteCall(final HttpUriRequest request, CloseableHttpClient httpClient, Map<String, String> headerMap) throws IOException {
        Header[] headers = getHeaders(headerMap);
        if (headers.length != 0)
            request.setHeaders(headers);
        try (CloseableHttpResponse response = httpClient.execute(request)) {
            if (response.getEntity().getContentLength() >= 2147483647L) {
                return null;
            }

            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int len = 0;
            while ((len = response.getEntity().getContent().read(buff, 0, 1024)) != -1) {
                swapStream.write(buff, 0, len);
            }
            return new HttpByteResult(response.getStatusLine().getStatusCode(), swapStream.toByteArray());
        }
    }

    /**
     * @return 返回ssl httpClient(没有连接池)
     * @throws KeyStoreException
     * @throws FileNotFoundException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws UnrecoverableKeyException
     * @throws SSLInitializationException
     * @throws KeyManagementException
     */
    private CloseableHttpClient getSSLHttpClient()
            throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException, SSLInitializationException, KeyManagementException {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream inStream = new FileInputStream(new File(this.certPathname))) {
            keyStore.load(inStream, this.certPassword.toCharArray());
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, this.certPassword.toCharArray()).build();
        // 指定TLS版本
        SSLConnectionSocketFactory sslSf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        // 设置httpclient的SSLSocketFactory
        return HttpClients.custom().setSSLSocketFactory(sslSf).build();
    }

    /**
     * 请求重试HANDLER
     *
     * @return HttpRequestRetryHandler
     */
    private HttpRequestRetryHandler getHttpRequestRetryHandler() {
        return (IOException exception, int executionCount, HttpContext context) -> {
            if (executionCount >= connectRetryNumber) {
                // Do not retry if over max retry count
                return false;
            }
            if (exception instanceof InterruptedIOException) {
                // Timeout
                return false;
            }
            if (exception instanceof UnknownHostException) {
                // Unknown host
                return false;
            }
            if (exception instanceof SSLException) {
                // SSL handshake exception
                return false;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            org.apache.http.HttpRequest request = clientContext.getRequest();
            return !(request instanceof HttpEntityEnclosingRequest);
        };
    }

    public String getBaseUri() {
        return baseUri;
    }

    public void setBaseUri(String baseUri) {
        this.baseUri = baseUri;
    }

    /**
     * 连接池参数设置
     *
     * @return PoolingHttpClientConnectionManager
     */
    private PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // Increase max total connection to 200
        cm.setMaxTotal(connectMaxTotal);
        // Increase default max connection per route to 20
        cm.setDefaultMaxPerRoute(connectMaxDefault);
        return cm;
    }

    /**
     * 连接存活策略
     *
     * @return ConnectionKeepAliveStrategy
     * @throws NumberFormatException
     */
    private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() throws NumberFormatException {
        return (HttpResponse response, HttpContext context) -> {
            // Honor 'keep-alive' header
            HeaderElementIterator it = new BasicHeaderElementIterator(
                    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    return Long.parseLong(value) * 1000;
                }
            }
            return connectKeepAliveDuration * 1000;
        };
    }

    /**
     * 创建文件夹
     *
     * @param filePath 文件路径
     * @return 是否成功
     */
    private boolean createFolder(String filePath) {
        File file = new File(filePath);
        File fileFolder = new File(file.getParentFile().getAbsolutePath());
        if (!fileFolder.exists()) {
            return fileFolder.mkdirs();
        }
        return true;
    }

    /**
     * 返回标准的uri
     *
     * @param strUrl
     * @return
     */
    private String getStandardUri(String strUrl) {
        try {
            URL url = new URL(strUrl);
            return new URI(url.getProtocol(), null, url.getHost(), url.getPort(), url.getPath(), url.getQuery(), null).toString();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 返回http请求头列表
     *
     * @param headerMap
     * @return
     */
    private Header[] getHeaders(Map<String, String> headerMap) {
        if (headerMap == null || headerMap.size() == 0)
            return new Header[0];
        Header[] headers = new Header[headerMap.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            headers[i++] = new BasicHeader(entry.getKey(), entry.getValue());
        }
        return headers;
    }

    public void setCertPathname(String certPathname) {
        this.certPathname = certPathname;
    }

    public void setCertPassword(String certPassword) {
        this.certPassword = certPassword;
    }

    public void setProxyUri(String proxyUri) {
        this.proxyUri = proxyUri;
    }

    public void setProxyPort(int proxyPort) {
        this.proxyPort = proxyPort;
    }

    public void setProxyScheme(String proxyScheme) {
        this.proxyScheme = proxyScheme;
    }

    public void setProxyUserName(String proxyUserName) {
        this.proxyUserName = proxyUserName;
    }

    public void setProxyPassword(String proxyPassword) {
        this.proxyPassword = proxyPassword;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public void setConnectRequestTimeout(int connectRequestTimeout) {
        this.connectRequestTimeout = connectRequestTimeout;
    }

    public void setSocketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    /**
     * 连接回收策略
     *
     */
    public class IdleConnectionMonitorThread extends Thread {
        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;
        private int connectRecyclingTime;

        private int connectFreeTimeout;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr,
                                           int connectRecyclingTime,
                                           int connectFreeTimeout) {
            super();
            this.connectRecyclingTime = connectRecyclingTime * 1000;
            this.connectFreeTimeout = connectFreeTimeout;
            shutdown = false;
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(connectRecyclingTime);
                        // Close expired connections
                        connMgr.closeExpiredConnections();
                        // Optionally, close connections
                        // that have been idle longer than 30 sec
                        connMgr.closeIdleConnections(connectFreeTimeout, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }
}
